Skip to content

ci: SHA-pin all actions + persist-credentials, add zizmor/actionlint/scorecard - #54

Merged
Navi Bot (project-navi-bot) merged 2 commits into
mainfrom
ci/harden-workflow-actions
May 25, 2026
Merged

ci: SHA-pin all actions + persist-credentials, add zizmor/actionlint/scorecard#54
Navi Bot (project-navi-bot) merged 2 commits into
mainfrom
ci/harden-workflow-actions

Conversation

@Fieldnote-Echo

Copy link
Copy Markdown
Member

What

Systematic GitHub Actions supply-chain hardening. Resolves all 65 zizmor findings across the workflows and adds three enforcement workflows so the posture cannot silently regress.

Why

Most actions were already SHA-pinned (harden-runner, upload-artifact, codeql, …) but a handful were left on floating tags, and no checkout set persist-credentials: false. The PR bots had been flagging these one at a time; zizmor surfaces the full set in one pass.

Changes

1. Pin every remaining floating action to a commit SHA (unpinned-uses ×30)

Action Before After
actions/checkout @v6 (×10) @de0fac2 # v6.0.2
Swatinem/rust-cache @v2 (×9) @e18b497 # v2
actions/setup-python @v6.2.0 @a309ff8 # v6.2.0
EmbarkStudios/cargo-deny-action @v2.0.18 @6c8f9fac # v2.0.18
dtolnay/rust-toolchain @stable / @1.89.0 @29eef33 + explicit toolchain:

dtolnay/rust-toolchain is special: the channel was encoded in the ref (@stable), so pinning to a SHA requires an explicit with: toolchain: stable (or 1.89.0 for the MSRV job) — matching the pattern the release workflows already use.

2. persist-credentials: false on every checkout (artipacked ×18)

No workflow pushes via git (releases use OIDC trusted-publishing; the changelog job creates the Release via the API token), so dropping the persisted credential is safe everywhere. Merged into existing with: blocks where present (e.g. changelog's fetch-depth: 0).

3. New enforcement workflows

  • zizmor.yml — GitHub Actions security audit (offline, pinned zizmor==1.25.2). A future unpinned action / broad token / credential-persisting checkout fails here.
  • actionlint.yml — workflow lint + shellcheck over run: blocks (pinned actionlint v1.7.12, installed via the runner's Go).
  • scorecard.yml — OpenSSF Scorecard; publishes the score badge + SARIF to code scanning.

All three are themselves SHA-pinned, set persist-credentials: false, and scope GITHUB_TOKEN.

Verification (local)

  • zizmor --offline --persona=regular .github/workflows/No findings (all 10 workflows).
  • actionlint .github/workflows/*.yml → clean.
  • All workflow YAML parses.

Test plan

  • CI green on this branch — in particular the pinned dtolnay/rust-toolchain + explicit toolchain: inputs must still install the right channel (watch the lint, test, and msrv (1.89.0) jobs).
  • New zizmor and actionlint jobs pass.
  • scorecard job runs and uploads SARIF on first run.

…scorecard

Systematic GitHub Actions supply-chain hardening — resolves all 65 zizmor
findings across the workflows.

Pinning (unpinned-uses, 30): pin every remaining floating action to a commit
SHA, matching the repo's existing hash-pinning intent:
- actions/checkout@v6              -> @de0fac2 # v6.0.2
- Swatinem/rust-cache@v2           -> @e18b497 # v2
- actions/setup-python@v6.2.0      -> @a309ff8 # v6.2.0
- EmbarkStudios/cargo-deny-action  -> @6c8f9fac # v2.0.18
- dtolnay/rust-toolchain@stable/@1.89.0 -> @29eef33 with an explicit
  'with: toolchain: {stable,1.89.0}' so the SHA pin keeps the channel
  (the ref no longer encodes it).

Credential hygiene (artipacked, 18): add 'persist-credentials: false' to every
checkout. No workflow pushes via git (releases use OIDC trusted-publishing;
changelog creates the Release via the API token), so this is safe throughout.

Enforcement (new workflows) to keep the above from regressing:
- zizmor.yml     - workflow security audit (offline, pinned zizmor 1.25.2)
- actionlint.yml - workflow lint + shellcheck (pinned actionlint v1.7.12 via Go)
- scorecard.yml  - OpenSSF Scorecard (publishes score badge + SARIF)

Verified locally: zizmor --offline clean across all 10 workflows; actionlint
clean; YAML parses. The new workflows are themselves SHA-pinned, set
persist-credentials: false, and scope GITHUB_TOKEN permissions.
@gemini-code-assist

Copy link
Copy Markdown

Note

Gemini is unable to generate a review for this pull request due to the file types involved not being currently supported.

@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Harden GitHub Actions workflows: SHA-pin all actions, disable credential persistence, add security enforcement

✨ Enhancement 🧪 Tests

Grey Divider

Walkthroughs

Description
• SHA-pin 30 floating actions across 10 workflows to commit SHAs
  - actions/checkout@v6@de0fac2 # v6.0.2 (10 instances)
  - Swatinem/rust-cache@v2@e18b497 # v2 (9 instances)
  - dtolnay/rust-toolchain@29eef33 with explicit toolchain: input
  - actions/setup-python@v6.2.0@a309ff8 # v6.2.0
  - EmbarkStudios/cargo-deny-action@v2.0.18@6c8f9fac # v2.0.18
• Add persist-credentials: false to all 18 checkout steps
• Introduce three new enforcement workflows for supply-chain security
  - zizmor.yml — offline GitHub Actions security audit (pinned zizmor==1.25.2)
  - actionlint.yml — workflow lint + shellcheck validation (pinned actionlint v1.7.12)
  - scorecard.yml — OpenSSF Scorecard analysis with SARIF upload
Diagram
flowchart LR
  A["Floating Actions<br/>v6, v2, stable"] -->|"SHA-pin to<br/>commit hashes"| B["Pinned Actions<br/>@de0fac2, @e18b497, etc."]
  C["Checkout with<br/>persisted credentials"] -->|"Add persist-credentials<br/>false"| D["Checkout with<br/>no credential persistence"]
  B --> E["New Enforcement<br/>Workflows"]
  D --> E
  E --> F["zizmor<br/>Security Audit"]
  E --> G["actionlint<br/>Workflow Lint"]
  E --> H["scorecard<br/>SARIF Upload"]

Loading

File Changes

1. .github/workflows/actionlint.yml 🧪 Tests +30/-0

New workflow lint enforcement with shellcheck

.github/workflows/actionlint.yml


2. .github/workflows/zizmor.yml 🧪 Tests +34/-0

New GitHub Actions security audit workflow

.github/workflows/zizmor.yml


3. .github/workflows/scorecard.yml 🧪 Tests +45/-0

New OpenSSF Scorecard analysis and SARIF upload

.github/workflows/scorecard.yml


View more (7)
4. .github/workflows/ci.yml ✨ Enhancement +53/-23

SHA-pin all actions and add credential hygiene

.github/workflows/ci.yml


5. .github/workflows/audit.yml ✨ Enhancement +2/-0

SHA-pin checkout and add persist-credentials false

.github/workflows/audit.yml


6. .github/workflows/changelog.yml ✨ Enhancement +1/-0

SHA-pin checkout and add persist-credentials false

.github/workflows/changelog.yml


7. .github/workflows/codeql.yml ✨ Enhancement +2/-0

Add persist-credentials false to checkout

.github/workflows/codeql.yml


8. .github/workflows/python.yml ✨ Enhancement +14/-7

SHA-pin all actions and add credential hygiene

.github/workflows/python.yml


9. .github/workflows/release-crate.yml ✨ Enhancement +4/-0

Add persist-credentials false to checkouts

.github/workflows/release-crate.yml


10. .github/workflows/release-python.yml ✨ Enhancement +4/-0

Add persist-credentials false to checkouts

.github/workflows/release-python.yml


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented May 25, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Missing contents permission ✓ Resolved 🐞 Bug ≡ Correctness
Description
In scorecard.yml the analysis job sets its own permissions but omits contents: read, so the
job-level permissions override the workflow-level setting and actions/checkout may not be able to
fetch the repository. This will prevent the scorecard job from running successfully end-to-end.
Code

.github/workflows/scorecard.yml[R13-29]

Evidence
scorecard.yml defines workflow-level contents: read but then defines job-level permissions without
contents: read while still running actions/checkout. The repo’s CodeQL workflow demonstrates the
intended pattern: when job-level permissions are set, it explicitly includes contents: read for
checkout.

.github/workflows/scorecard.yml[13-29]
.github/workflows/codeql.yml[32-55]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`jobs.analysis.permissions` overrides the workflow-level `permissions`, but it currently does not include `contents: read`. The job runs `actions/checkout`, which requires repository read permission.

### Issue Context
This repo already follows the pattern of explicitly re-stating `contents: read` when a job defines its own permissions (e.g., CodeQL), implying the intended permission model is “job permissions fully specify what the job needs”.

### Fix Focus Areas
- .github/workflows/scorecard.yml[13-29]

### Suggested change
Add `contents: read` under `jobs.analysis.permissions` (alongside `security-events: write` and `id-token: write`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread .github/workflows/scorecard.yml

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cb38d68ce9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment thread .github/workflows/scorecard.yml
…dex review)

Job-level permissions REPLACE the workflow-level set (omitted scopes become
none), so the analysis job's {security-events, id-token} block was dropping the
workflow-level contents: read that checkout + Scorecard need to read the repo.
Add it explicitly (matching the release workflows' publish jobs). Latent —
scorecard.yml runs only on push-to-main/schedule, not PRs, so CI didn't surface it.
@project-navi-bot
Navi Bot (project-navi-bot) merged commit ca5594b into main May 25, 2026
24 checks passed
@project-navi-bot
Navi Bot (project-navi-bot) deleted the ci/harden-workflow-actions branch May 25, 2026 18:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants